using System;
using System.Collections.Generic;
using System.Data.Common;
using System.Data;
using Library.OrmEnterpriseEntity.Factor;

namespace Library.OrmEnterpriseEntity {
	abstract class BaseService : IDisposable {
		public static readonly object[] EmptyValues = new object[0];
		public static readonly string[] EmptyParameters = new string[0];
		public static readonly DbType[] EmptyTypes = new DbType[0];
		protected object[] Values;
		protected string[] Parameters;
		protected DbType[] Types;
		protected int ConnectionTimeOut = 30;
		protected virtual CommandType DefaultCommandType { get { return CommandType.StoredProcedure; } }
		protected CommandType? ExeCommandType { get; set; }
		public int SelectFetchRow { get; internal set; }
		private readonly IConnectionCreator _dbContext;
		protected BaseService(IConnectionCreator dbContext) {
			InitNewIntance();
			Values = EmptyValues;
			Parameters = EmptyParameters;
			Types = EmptyTypes;
			_dbContext = dbContext;
		}


		private void InitNewIntance() {
			_conn = null;
			_currentTransaction = null;
			_isBorrowConnection = false;

		}

		private bool _isBorrowConnection;
		private DbConnection _conn;
		private DbTransaction _currentTransaction;

		protected static bool HasTransaction {
			get { return TransactionManager.Current.HasTransaction; }
		}

		protected DbConnection OpenConnection() {
			TransactionManager current = TransactionManager.Current;
			if (_conn == null || _conn.State == ConnectionState.Closed) {
				_conn = current.CurrentConnection;
				if (_conn == null) {
					if (ConnectionManager.Current.Connection != null) {
						_conn = ConnectionManager.Current.Connection;
						_isBorrowConnection = true;
						return _conn;
					}
					_conn = _dbContext.CreateConnection();
					_conn.Open();
				} else {
					_currentTransaction = current.CurrentTransaction;
					_isBorrowConnection = true;
				}
			}

			return _conn;
		}

		protected void ReleaseConnection(DbConnection connection) {
			if (connection == null) return;

			if (!_isBorrowConnection) {
				connection.Close();
			}
		}

		protected void CloseCurrentConnection() {
			lock (this) {
				if (_conn != null) {
					ReleaseConnection(_conn);
					_conn = null;
					_currentTransaction = null;
				}
			}
		}

		protected void ExcuteNonQuery(string commandText) {
			if (!ExeCommandType.HasValue) ExeCommandType = DefaultCommandType;
			ExcuteNonQuery(commandText, ExeCommandType.Value);
			ExeCommandType = null;
		}
		protected void ExcuteNonQuery(string commandText, CommandType commandType) {
			ExcuteNonQuery(commandText, Values, Parameters, Types, commandType);
		}
		protected void ExcuteNonQuery(string commandText, object[] paramterValues, string[] paramterNames, DbType[] dbTypes) {
			if (!ExeCommandType.HasValue) ExeCommandType = DefaultCommandType;
			ExcuteNonQuery(commandText, paramterValues, paramterNames, dbTypes, ExeCommandType.Value);
			ExeCommandType = null;
		}
		protected void ExcuteNonQuery(string commandText, object[] paramterValues, string[] paramterNames, DbType[] dbTypes, CommandType commandType) {
			DbConnection dbConnection = OpenConnection();
			DbCommand comm = dbConnection.CreateCommand();
			comm.CommandTimeout = ConnectionTimeOut;
			if (_isBorrowConnection) {
				comm.Transaction = _currentTransaction;
			}
			comm.Connection = dbConnection;
			AddCommand(comm, paramterValues, paramterNames, dbTypes);

			comm.CommandText = commandText;
			comm.CommandType = commandType;
			comm.ExecuteNonQuery();
			comm.Connection = null;
			comm.Dispose();
			CloseCurrentConnection();
		}
		protected void ExcuteNonQuery(string commandText, IEnumerable<QueryParameter> parameters, CommandType commandType) {
			DbConnection dbConnection = OpenConnection();
			DbCommand comm = dbConnection.CreateCommand();
			comm.CommandTimeout = ConnectionTimeOut;
			if (_isBorrowConnection) {
				comm.Transaction = _currentTransaction;
			}
			comm.Connection = dbConnection;
			AddCommand(comm, parameters);

			comm.CommandText = commandText;
			comm.CommandType = commandType;
			comm.ExecuteNonQuery();
			comm.Connection = null;
			comm.Dispose();
			CloseCurrentConnection();
		}
		protected DbDataReader ExcuteDataReader(string commandText) {
			if (!ExeCommandType.HasValue) ExeCommandType = DefaultCommandType;
			var result = ExcuteDataReader(commandText, ExeCommandType.Value);
			ExeCommandType = null;
			return result;
		}
		protected DbDataReader ExcuteDataReader(string commandText, CommandType commandType) {
			return ExcuteDataReader(commandText, Values, Parameters, Types, commandType);
		}
		protected DbDataReader ExcuteDataReader(string commandText, object[] parameterValues, string[] paramterNames, DbType[] dbTypes, CommandType commandType, CommandBehavior behavior) {
			DbConnection dbConnection = OpenConnection();
			DbCommand comm = dbConnection.CreateCommand();
			comm.CommandTimeout = ConnectionTimeOut;
			if (_isBorrowConnection) {
				comm.Transaction = _currentTransaction;
			}
			comm.Connection = dbConnection;
			AddCommand(comm, parameterValues, paramterNames, dbTypes);

			comm.CommandText = commandText;
			comm.CommandType = commandType;
			DbDataReader dataReader = comm.ExecuteReader(behavior);
			return new BaseDbDataReader(dataReader, this);
		}

		protected DbDataReader ExcuteDataReader(string commandText, IEnumerable<QueryParameter> parameters, CommandType commandType, CommandBehavior behavior) {
			DbConnection dbConnection = OpenConnection();
			DbCommand comm = dbConnection.CreateCommand();
			comm.CommandTimeout = ConnectionTimeOut;
			if (_isBorrowConnection) {
				comm.Transaction = _currentTransaction;
			}
			comm.Connection = dbConnection;
			AddCommand(comm, parameters);
			comm.CommandText = commandText;
			comm.CommandType = commandType;
			DbDataReader dataReader = comm.ExecuteReader(behavior);
			return new BaseDbDataReader(dataReader, this);
		}
		protected DbDataReader ExcuteDataReader(string commandText, object[] parameterValues, string[] paramterNames, DbType[] dbTypes, CommandType commandType) {
			return ExcuteDataReader(commandText, parameterValues, paramterNames, dbTypes, commandType, CommandBehavior.Default);
		}
		protected DbDataReader ExcuteDataReader(string commandText, object[] paramterValues, string[] paramterNames, DbType[] dbTypes) {
			if (!ExeCommandType.HasValue) ExeCommandType = DefaultCommandType;
			var result = ExcuteDataReader(commandText, paramterValues, paramterNames, dbTypes, ExeCommandType.Value);
			ExeCommandType = null;
			return result;
		}

		protected object ExcuteScalar(string commandText) {
			if (!ExeCommandType.HasValue) ExeCommandType = DefaultCommandType;
			var result = ExcuteScalar(commandText, ExeCommandType.Value);
			ExeCommandType = null;
			return result;
		}
		protected object ExcuteScalar(string commandText, CommandType commandType) {
			return ExcuteScalar(commandText, Values, Parameters, Types, commandType);
		}
		protected object ExcuteScalar(string commandText, object[] paramterValues, string[] paramterNames, DbType[] dbTypes, CommandType commandType) {
			DbConnection dbConnection = OpenConnection();
			DbCommand comm = dbConnection.CreateCommand();
			comm.CommandTimeout = ConnectionTimeOut;
			if (_isBorrowConnection) {
				comm.Transaction = _currentTransaction;
			}
			comm.Connection = dbConnection;
			AddCommand(comm, paramterValues, paramterNames, dbTypes);

			comm.CommandText = commandText;
			comm.CommandType = commandType;
			object result = comm.ExecuteScalar();
			comm.Connection = null;
			comm.Dispose();
			CloseCurrentConnection();
			return result;
		}
		protected object ExcuteScalar(string commandText, IEnumerable<QueryParameter> parameters, CommandType commandType) {
			DbConnection dbConnection = OpenConnection();
			DbCommand comm = dbConnection.CreateCommand();
			comm.CommandTimeout = ConnectionTimeOut;
			if (_isBorrowConnection) {
				comm.Transaction = _currentTransaction;
			}
			comm.Connection = dbConnection;
			AddCommand(comm, parameters);

			comm.CommandText = commandText;
			comm.CommandType = commandType;
			object result = comm.ExecuteScalar();
			comm.Connection = null;
			comm.Dispose();
			CloseCurrentConnection();
			return result;
		}
		/// <summary>
		/// 
		/// </summary>
		/// <param name="commandText"></param>
		/// <param name="paramterValues"></param>
		/// <param name="paramterNames"></param>
		/// <param name="dbTypes"></param>
		/// <returns></returns>
		protected object ExcuteScalar(string commandText, object[] paramterValues, string[] paramterNames, DbType[] dbTypes) {
			if (!ExeCommandType.HasValue) ExeCommandType = DefaultCommandType;
			var result = ExcuteScalar(commandText, paramterValues, paramterNames, dbTypes, ExeCommandType.Value);
			ExeCommandType = null;
			return result;
		}

		protected static bool IsDataNull(object obj) {
			return ReferenceEquals(DBNull.Value, obj) || obj == null;
		}

		#region IDisposable Members

		public void Dispose() {
			Dispose(true);
			GC.SuppressFinalize(this);
		}
		~BaseService() {
			Dispose(false);
		}
		private bool _isDispose;
		private void Dispose(bool isDispose) {
			if (_isDispose) return;
			_isDispose = true;
			if (isDispose)
				CloseCurrentConnection();
		}



		private static void AddCommand(DbCommand command, IEnumerable<QueryParameter> parameters) {
			foreach (var queryParameter in parameters) {
				DbParameter parameter = command.CreateParameter();
				parameter.ParameterName = queryParameter.SqlName;
				parameter.DbType = queryParameter.Type;
				if (queryParameter.Value == null) {
					parameter.Value = DBNull.Value;
					parameter.IsNullable = true;
				} else {
					parameter.Value = queryParameter.Value;
				}
				command.Parameters.Add(parameter);
			}
		}

		private static void AddCommand(DbCommand command, object[] paramterValues, string[] parameterNames, DbType[] dbTypes) {
			if (parameterNames == null || paramterValues == null || dbTypes == null || (parameterNames.Length != paramterValues.Length) && parameterNames.Length != dbTypes.Length) {
				throw new ArgumentException("The length of paramter name and value and DBType not matched");
			}

			for (int i = 0; i < paramterValues.Length; i++) {
				object value = paramterValues[i];
				string name = parameterNames[i];
				DbParameter parameter = command.CreateParameter();
				parameter.ParameterName = name;
				parameter.DbType = dbTypes[i];
				if (value == null) {
					parameter.Value = DBNull.Value;
					parameter.IsNullable = true;
				} else {
					parameter.Value = value;
				}
				command.Parameters.Add(parameter);
			}
		}

		#endregion
	}
}
